package com.laifeng.sopcastsdk.video;

import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;

import com.laifeng.sopcastsdk.configuration.VideoConfiguration;
import com.laifeng.sopcastsdk.constant.SopCastConstant;
import com.laifeng.sopcastsdk.mediacodec.VideoMediaCodec;
import com.laifeng.sopcastsdk.utils.SopCastLog;

import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReentrantLock;

@TargetApi(18)
public class MyRecorder {

    private MediaCodec mMediaCodec;
    private MediaCodec mLiveMediaCodec;

    private InputSurface mInputSurface;
    private InputSurface mLiveInputSurface;

    private OnVideoEncodeListener mListener;

    private MediaCodec.BufferInfo mBufferInfo;
    private MediaCodec.BufferInfo mLiveBufferInfo;

    private VideoConfiguration mRecordConfiguration;
    private VideoConfiguration mLiveConfiguration;


    private HandlerThread mHandlerThread;
    private HandlerThread mLiveHandlerThread;

    private Handler mEncoderHandler;
    private Handler mLiveEncoderHandler;
    private ReentrantLock encodeLock = new ReentrantLock();
    private ReentrantLock liveEncodeLock = new ReentrantLock();
    private volatile boolean isStarted;

    public MyRecorder() {
        mRecordConfiguration = VideoConfiguration.getRecordingConfig();
        mLiveConfiguration = VideoConfiguration.getLivingConfig();
    }

    public void setVideoEncodeListener(OnVideoEncodeListener listener) {
        mListener = listener;
    }

    public void prepareEncoder() {
        if (mMediaCodec != null || mInputSurface != null) {
            throw new RuntimeException("prepareEncoder called twice?");
        }

        mMediaCodec = VideoMediaCodec.getVideoMediaCodec(mRecordConfiguration);
        mLiveMediaCodec = VideoMediaCodec.getVideoMediaCodec(mLiveConfiguration);

        mHandlerThread = new HandlerThread("SopCastEncode");
        mHandlerThread.start();
        mEncoderHandler = new Handler(mHandlerThread.getLooper());

        mLiveHandlerThread = new HandlerThread("LIVE_ENCODER_TASK");
        mLiveHandlerThread.start();
        mLiveEncoderHandler = new Handler(mLiveHandlerThread.getLooper());

        mBufferInfo = new MediaCodec.BufferInfo();
        mLiveBufferInfo = new MediaCodec.BufferInfo();
        isStarted = true;
    }

    public boolean firstTimeSetup() {
        if (mMediaCodec == null || mInputSurface != null) {
            return false;
        }

        try {
            mInputSurface = new InputSurface(mMediaCodec.createInputSurface());
            mMediaCodec.start();
        } catch (Exception e) {
            releaseEncoder();
            throw (RuntimeException) e;
        }
        return true;
    }

    public boolean firstTimeSetupLive() {
        if (mLiveMediaCodec == null || mLiveInputSurface != null) {
            return false;
        }

        try {
            mLiveInputSurface = new InputSurface(mLiveMediaCodec.createInputSurface());
            mLiveMediaCodec.start();
        } catch (Exception e) {
            releaseEncoder();
            throw (RuntimeException) e;
        }

        return true;
    }


    public void startSwapData() {
        mEncoderHandler.post(swapDataRunnable);
    }

    public void startLiveSwapData() {
        mLiveEncoderHandler.post(swapLiveDataRunnable);
    }

    public void makeCurrent() {
        mInputSurface.makeCurrent();
    }

    public void makeLiveCurrent() {
        mLiveInputSurface.makeCurrent();
    }

    public void swapBuffers() {
        if (mMediaCodec == null) {
            return;
        }

        mInputSurface.swapBuffers();
        mInputSurface.setPresentationTime(System.nanoTime());
    }

    public void swapLiveBuffers() {
        if (mLiveMediaCodec == null) {
            return;
        }
        mLiveInputSurface.swapBuffers();
        mLiveInputSurface.setPresentationTime(System.nanoTime());
    }

    private Runnable swapDataRunnable = new Runnable() {
        @Override
        public void run() {
            drainEncoder();
        }
    };

    private Runnable swapLiveDataRunnable = new Runnable() {
        @Override
        public void run() {
            drainLiveEncoder();
        }
    };

    public void stop() {
        if (!isStarted) {
            return;
        }
        isStarted = false;
        mEncoderHandler.removeCallbacks(null);
        mHandlerThread.quit();
        encodeLock.lock();
        releaseEncoder();
        encodeLock.unlock();
    }

    private void releaseEncoder() {
        if (mMediaCodec != null) {
            mMediaCodec.signalEndOfInputStream();
            mMediaCodec.stop();
            mMediaCodec.release();
            mMediaCodec = null;
        }
        if (mInputSurface != null) {
            mInputSurface.release();
            mInputSurface = null;
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public boolean setRecorderBps(int bps) {
        if (mMediaCodec == null || mInputSurface == null) {
            return false;
        }
        SopCastLog.d(SopCastConstant.TAG, "bps :" + bps * 1024);
        Bundle bitrate = new Bundle();
        bitrate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bps * 1024);
        mMediaCodec.setParameters(bitrate);
        return true;
    }

    private void drainEncoder() {

        ByteBuffer[] outBuffers = mMediaCodec.getOutputBuffers();

        while (isStarted) {
            encodeLock.lock();
            if (mMediaCodec != null) {
                int outBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 2000);
                if (outBufferIndex >= 0) {
                    ByteBuffer bb = outBuffers[outBufferIndex];
                    if (mListener != null && enableRecording) {
                        mListener.onVideoEncode(bb, mBufferInfo);
                    }

                    mMediaCodec.releaseOutputBuffer(outBufferIndex, false);
                } else {
                    try {
                        // wait 10ms
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                break;
            }

            encodeLock.unlock();
        }
    }

    private void drainLiveEncoder() {
        ByteBuffer[] liveOutBuffers = mLiveMediaCodec.getOutputBuffers();
        while (isStarted) {
            liveEncodeLock.lock();
            if (mLiveMediaCodec != null) {
                int outBufferIndex = mLiveMediaCodec.dequeueOutputBuffer(mLiveBufferInfo, 2000);
                if (outBufferIndex >= 0) {
                    ByteBuffer bb = liveOutBuffers[outBufferIndex];
                    if (mListener != null && enableLiving) {
                        mListener.onLiveVideoEncode(bb, mLiveBufferInfo);
                    }
                    mLiveMediaCodec.releaseOutputBuffer(outBufferIndex, false);
                } else {
                    try {
                        // wait 10ms
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                liveEncodeLock.unlock();
            } else {
                liveEncodeLock.unlock();
                break;
            }
        }
    }

    private boolean enableLiving = false;
    private boolean enableRecording = false;

    public void setLivingState(boolean enable) {
        enableLiving = enable;
    }

    public void setRecordState(boolean enable) {
        enableRecording = enable;
    }

    public boolean isLiving() {
        return enableLiving;
    }

    public boolean isRecording() {
        return enableRecording;
    }
}
